<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <base data-ice="baseUrl" href="../../">
  <title data-ice="title">src/typed-using-raf.js | typed.js</title>
  <link type="text/css" rel="stylesheet" href="css/style.css">
  <link type="text/css" rel="stylesheet" href="css/prettify-tomorrow.css">
  <script src="script/prettify/prettify.js"></script>
  <script src="script/manual.js"></script>
<meta name="description" content="A JavaScript Typing Animation Library"><meta property="twitter:card" content="summary"><meta property="twitter:title" content="typed.js"><meta property="twitter:description" content="A JavaScript Typing Animation Library"></head>
<body class="layout-container" data-ice="rootContainer">

<header>
  <a href="./">Home</a>
  
  <a href="identifiers.html">Reference</a>
  <a href="source.html">Source</a>
  
  <div class="search-box">
  <span>
    <img src="./image/search.png">
    <span class="search-input-edge"></span><input class="search-input"><span class="search-input-edge"></span>
  </span>
    <ul class="search-result"></ul>
  </div>
<a style="position:relative; top:3px;" href="https://github.com/mattboldt/typed.js"><img width="20px" src="./image/github.png"></a></header>

<nav class="navigation" data-ice="nav"><div>
  <ul>
    
  <li data-ice="doc"><span data-ice="kind" class="kind-class">C</span><span data-ice="name"><span><a href="class/src/html-parser.js~HTMLParser.html">HTMLParser</a></span></span></li>
<li data-ice="doc"><span data-ice="kind" class="kind-class">C</span><span data-ice="name"><span><a href="class/src/initializer.js~Initializer.html">Initializer</a></span></span></li>
<li data-ice="doc"><span data-ice="kind" class="kind-class">C</span><span data-ice="name"><span><a href="class/src/typed-using-raf.js~Typed.html">Typed</a></span></span></li>
<li data-ice="doc"><span data-ice="kind" class="kind-class">C</span><span data-ice="name"><span><a href="class/src/typed.js~Typed.html">Typed</a></span></span></li>
<li data-ice="doc"><span data-ice="kind" class="kind-variable">V</span><span data-ice="name"><span><a href="variable/index.html#static-variable-defaults">defaults</a></span></span></li>
<li data-ice="doc"><span data-ice="kind" class="kind-variable">V</span><span data-ice="name"><span><a href="variable/index.html#static-variable-htmlParser">htmlParser</a></span></span></li>
<li data-ice="doc"><span data-ice="kind" class="kind-variable">V</span><span data-ice="name"><span><a href="variable/index.html#static-variable-initializer">initializer</a></span></span></li>
</ul>
</div>
</nav>

<div class="content" data-ice="content"><h1 data-ice="title">src/typed-using-raf.js</h1>
<pre class="source-code line-number raw-source-code"><code class="prettyprint linenums" data-ice="content">import raf from &apos;raf&apos;;
import { initializer } from &apos;./initializer.js&apos;;
import { htmlParser } from &apos;./html-parser.js&apos;;

/**
 * Welcome to Typed.js!
 * @param {string} elementId HTML element ID _OR_ HTML element
 * @param {object} options options object
 * @returns {object} a new Typed object
 */
export default class Typed {
  constructor(elementId, options) {
    // Initialize it up
    initializer.load(this, options, elementId);
    // All systems go!
    this.begin();
  }

  /**
   * Toggle start() and stop() of the Typed instance
   * @public
   */
  toggle() {
    this.pause.status ? this.start() : this.stop();
  }

  /**
   * Stop typing / backspacing and enable cursor blinking
   * @public
   */
  stop() {
    if (this.typingComplete) return;
    if (this.pause.status) return;
    this.toggleBlinking(true);
    this.pause.status = true;
    this.options.onStop(this.arrayPos, this);
  }

  /**
   * Start typing / backspacing after being stopped
   * @public
   */
  start() {
    if (this.typingComplete) return;
    if (!this.pause.status) return;
    this.pause.status = false;
    if (this.pause.typewrite) {
      this.typewrite(this.pause.curString, this.pause.curStrPos);
    } else {
      this.backspace(this.pause.curString, this.pause.curStrPos);
    }
    this.options.onStart(this.arrayPos, this);
  }

  /**
   * Destroy this instance of Typed
   * @public
   */
  destroy() {
    this.reset(false);
    this.options.onDestroy(this);
  }

  /**
   * Reset Typed and optionally restarts
   * @param {boolean} restart
   * @public
   */
  reset(restart = true) {
    clearInterval(this.timeout);
    this.replaceText(&apos;&apos;);
    if (this.cursor &amp;&amp; this.cursor.parentNode) {
      this.cursor.parentNode.removeChild(this.cursor);
      this.cursor = null;
    }
    this.strPos = 0;
    this.arrayPos = 0;
    this.curLoop = 0;
    if (restart) {
      this.insertCursor();
      this.options.onReset(this);
      this.begin();
    }
  }

  /**
   * Sets up the typing animation
   * @private
   */
  begin() {
    this.options.onBegin(this);
    this.typingComplete = false;
    this.shuffleStringsIfNeeded(this);
    this.insertCursor();
    if (this.bindInputFocusEvents) this.bindFocusEvents();
    raf((timestamp) =&gt; this.beginAnimation(timestamp));
  }

  /**
   * Begins the typing animation
   * @private
   */
  beginAnimation(timestamp) {
    if (this._beginAnimationStart === undefined) {
      this._beginAnimationStart = timestamp;
    }

    if (this.startDelay &gt; 0) {
      const elapsed = timestamp - this._beginAnimationStart;
      if (elapsed &lt; this.startDelay) {
        raf((timestamp) =&gt; this.beginAnimation(timestamp));
        return;
      }
    }

    this._beginAnimationStart = undefined;

    // Check if there is some text in the element, if yes start by backspacing the default message
    if (!this.currentElContent || this.currentElContent.length === 0) {
      this.typewrite(this.strings[this.sequence[this.arrayPos]], this.strPos);
    } else {
      // Start typing
      this.backspace(this.currentElContent, this.currentElContent.length);
    }
  }

  /**
   * Called for each character typed
   * @param {string} curString the current string in the strings array
   * @param {number} curStrPos the current position in the curString
   * @private
   */
  typewrite(curString, curStrPos) {
    if (this.fadeOut &amp;&amp; this.el.classList.contains(this.fadeOutClass)) {
      this.el.classList.remove(this.fadeOutClass);
      if (this.cursor) this.cursor.classList.remove(this.fadeOutClass);
    }

    if (this.pause.status === true) {
      this.setPauseStatus(curString, curStrPos, true);
      return;
    }

    raf((timestamp) =&gt; this.typewriteStep(curString, curStrPos, timestamp));
  }

  typewriteStep(curString, curStrPos, timestamp) {
    const humanize = this.humanizer(this.typeSpeed);
    let numChars = 1;

    if (this._typewriteStart === undefined) {
      this._typewriteStart = timestamp;
    }

    if (humanize &gt; 0) {
      const elapsed = timestamp - this._typewriteStart;
      if (elapsed &lt;= humanize) {
        raf((t) =&gt; this.typewriteStep(curString, curStrPos, t));
        return;
      }
    }

    this._typewriteStart = undefined;

    // skip over any HTML chars
    curStrPos = htmlParser.typeHtmlChars(curString, curStrPos, this);

    let pauseTime = 0;
    let substr = curString.substring(curStrPos);
    // check for an escape character before a pause value
    // format: \^\d+ .. eg: ^1000 .. should be able to print the ^ too using ^^
    // single ^ are removed from string
    if (substr.charAt(0) === &apos;^&apos;) {
      if (/^\^\d+/.test(substr)) {
        let skip = 1; // skip at least 1
        substr = /\d+/.exec(substr)[0];
        skip += substr.length;
        pauseTime = parseInt(substr);
        this.temporaryPause = true;
        this.options.onTypingPaused(this.arrayPos, this);
        // strip out the escape character and pause value so they&apos;re not printed
        curString =
          curString.substring(0, curStrPos) +
          curString.substring(curStrPos + skip);
        this.toggleBlinking(true);
      }
    }

    // check for skip characters formatted as
    // &quot;this is a `string to print NOW` ...&quot;
    if (substr.charAt(0) === &apos;`&apos;) {
      while (curString.substring(curStrPos + numChars).charAt(0) !== &apos;`&apos;) {
        numChars++;
        if (curStrPos + numChars &gt; curString.length) break;
      }
      // strip out the escape characters and append all the string in between
      const stringBeforeSkip = curString.substring(0, curStrPos);
      const stringSkipped = curString.substring(
        stringBeforeSkip.length + 1,
        curStrPos + numChars
      );
      const stringAfterSkip = curString.substring(curStrPos + numChars + 1);
      curString = stringBeforeSkip + stringSkipped + stringAfterSkip;
      numChars--;
    }

    // timeout for any pause after a character
    this.timeout = setTimeout(() =&gt; {
      // Accounts for blinking while paused
      this.toggleBlinking(false);

      // We&apos;re done with this sentence!
      if (curStrPos &gt;= curString.length) {
        this.doneTyping(curString, curStrPos);
      } else {
        this.keepTyping(curString, curStrPos, numChars);
      }
      // end of character pause
      if (this.temporaryPause) {
        this.temporaryPause = false;
        this.options.onTypingResumed(this.arrayPos, this);
      }
    }, pauseTime);
  }

  /**
   * Continue to the next string &amp; begin typing
   * @param {string} curString the current string in the strings array
   * @param {number} curStrPos the current position in the curString
   * @private
   */
  keepTyping(curString, curStrPos, numChars) {
    // call before functions if applicable
    if (curStrPos === 0) {
      this.toggleBlinking(false);
      this.options.preStringTyped(this.arrayPos, this);
    }
    // start typing each new char into existing string
    // curString: arg, this.el.html: original text inside element
    curStrPos += numChars;
    const nextString = curString.substring(0, curStrPos);
    this.replaceText(nextString);
    // loop the function
    this.typewrite(curString, curStrPos);
  }

  /**
   * We&apos;re done typing the current string
   * @param {string} curString the current string in the strings array
   * @param {number} curStrPos the current position in the curString
   * @private
   */
  doneTyping(curString, curStrPos) {
    // fires callback function
    this.options.onStringTyped(this.arrayPos, this);
    this.toggleBlinking(true);
    // is this the final string
    if (this.arrayPos === this.strings.length - 1) {
      // callback that occurs on the last typed string
      this.complete();
      // quit if we wont loop back
      if (this.loop === false || this.curLoop === this.loopCount) {
        return;
      }
    }
    this.timeout = setTimeout(() =&gt; {
      this.backspace(curString, curStrPos);
    }, this.backDelay);
  }

  /**
   * Backspaces 1 character at a time
   * @param {string} curString the current string in the strings array
   * @param {number} curStrPos the current position in the curString
   * @private
   */
  backspace(curString, curStrPos) {
    if (this.pause.status === true) {
      this.setPauseStatus(curString, curStrPos, false);
      return;
    }
    if (this.fadeOut) return this.initFadeOut();

    this.toggleBlinking(false);
    const humanize = this.humanizer(this.backSpeed);

    this.timeout = setTimeout(() =&gt; {
      curStrPos = htmlParser.backSpaceHtmlChars(curString, curStrPos, this);
      // replace text with base text + typed characters
      const curStringAtPosition = curString.substring(0, curStrPos);
      this.replaceText(curStringAtPosition);

      // if smartBack is enabled
      if (this.smartBackspace) {
        // the remaining part of the current string is equal of the same part of the new string
        let nextString = this.strings[this.arrayPos + 1];
        if (
          nextString &amp;&amp;
          curStringAtPosition === nextString.substring(0, curStrPos)
        ) {
          this.stopNum = curStrPos;
        } else {
          this.stopNum = 0;
        }
      }

      // if the number (id of character in current string) is
      // less than the stop number, keep going
      if (curStrPos &gt; this.stopNum) {
        // subtract characters one by one
        curStrPos--;
        // loop the function
        this.backspace(curString, curStrPos);
      } else if (curStrPos &lt;= this.stopNum) {
        // if the stop number has been reached, increase
        // array position to next string
        this.arrayPos++;
        // When looping, begin at the beginning after backspace complete
        if (this.arrayPos === this.strings.length) {
          this.arrayPos = 0;
          this.options.onLastStringBackspaced();
          this.shuffleStringsIfNeeded();
          this.begin();
        } else {
          this.typewrite(this.strings[this.sequence[this.arrayPos]], curStrPos);
        }
      }
      // humanized value for typing
    }, humanize);
  }

  /**
   * Full animation is complete
   * @private
   */
  complete() {
    this.options.onComplete(this);
    if (this.loop) {
      this.curLoop++;
    } else {
      this.typingComplete = true;
    }
  }

  /**
   * Has the typing been stopped
   * @param {string} curString the current string in the strings array
   * @param {number} curStrPos the current position in the curString
   * @param {boolean} isTyping
   * @private
   */
  setPauseStatus(curString, curStrPos, isTyping) {
    this.pause.typewrite = isTyping;
    this.pause.curString = curString;
    this.pause.curStrPos = curStrPos;
  }

  /**
   * Toggle the blinking cursor
   * @param {boolean} isBlinking
   * @private
   */
  toggleBlinking(isBlinking) {
    if (!this.cursor) return;
    // if in paused state, don&apos;t toggle blinking a 2nd time
    if (this.pause.status) return;
    if (this.cursorBlinking === isBlinking) return;
    this.cursorBlinking = isBlinking;
    if (isBlinking) {
      this.cursor.classList.add(&apos;typed-cursor--blink&apos;);
    } else {
      this.cursor.classList.remove(&apos;typed-cursor--blink&apos;);
    }
  }

  /**
   * Speed in MS to type
   * @param {number} speed
   * @private
   */
  humanizer(speed) {
    return Math.round((Math.random() * speed) / 2) + speed;
  }

  /**
   * Shuffle the sequence of the strings array
   * @private
   */
  shuffleStringsIfNeeded() {
    if (!this.shuffle) return;
    this.sequence = this.sequence.sort(() =&gt; Math.random() - 0.5);
  }

  /**
   * Adds a CSS class to fade out current string
   * @private
   */
  initFadeOut() {
    this.el.className += ` ${this.fadeOutClass}`;
    if (this.cursor) this.cursor.className += ` ${this.fadeOutClass}`;
    return setTimeout(() =&gt; {
      this.arrayPos++;
      this.replaceText(&apos;&apos;);

      // Resets current string if end of loop reached
      if (this.strings.length &gt; this.arrayPos) {
        this.typewrite(this.strings[this.sequence[this.arrayPos]], 0);
      } else {
        this.typewrite(this.strings[0], 0);
        this.arrayPos = 0;
      }
    }, this.fadeOutDelay);
  }

  /**
   * Replaces current text in the HTML element
   * depending on element type
   * @param {string} str
   * @private
   */
  replaceText(str) {
    if (this.attr) {
      this.el.setAttribute(this.attr, str);
    } else {
      if (this.isInput) {
        this.el.value = str;
      } else if (this.contentType === &apos;html&apos;) {
        this.el.innerHTML = str;
      } else {
        this.el.textContent = str;
      }
    }
  }

  /**
   * If using input elements, bind focus in order to
   * start and stop the animation
   * @private
   */
  bindFocusEvents() {
    if (!this.isInput) return;
    this.el.addEventListener(&apos;focus&apos;, (e) =&gt; {
      this.stop();
    });
    this.el.addEventListener(&apos;blur&apos;, (e) =&gt; {
      if (this.el.value &amp;&amp; this.el.value.length !== 0) {
        return;
      }
      this.start();
    });
  }

  /**
   * On init, insert the cursor element
   * @private
   */
  insertCursor() {
    if (!this.showCursor) return;
    if (this.cursor) return;
    this.cursor = document.createElement(&apos;span&apos;);
    this.cursor.className = &apos;typed-cursor&apos;;
    this.cursor.setAttribute(&apos;aria-hidden&apos;, true);
    this.cursor.innerHTML = this.cursorChar;
    this.el.parentNode &amp;&amp;
      this.el.parentNode.insertBefore(this.cursor, this.el.nextSibling);
  }
}
</code></pre>

</div>

<footer class="footer">
  Generated by <a href="https://esdoc.org">ESDoc<span data-ice="esdocVersion">(1.1.0)</span><img src="./image/esdoc-logo-mini-black.png"></a>
</footer>

<script src="script/search_index.js"></script>
<script src="script/search.js"></script>
<script src="script/pretty-print.js"></script>
<script src="script/inherited-summary.js"></script>
<script src="script/test-summary.js"></script>
<script src="script/inner-link.js"></script>
<script src="script/patch-for-local.js"></script>
</body>
</html>
